/************************************************************
  > File Name: xping.c
  > Author: eudiwffe
  > Mail: tjyemail@sina.com 
  > Created Time: 2016年11月09日 星期三 10时56分15秒
 ***********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#ifdef WIN32
#include<winsock2.h>
typedef int socklen_t;
#endif
#ifdef __linux__
#include<sys/wait.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#endif

//#include<unistd.h>
//#include<netdb.h>
//#include<arpa/inet.h>

#define BUFSIZE 64
#define CMDLEN 1024

typedef struct _Parameter
{
	char sr;		// 's' - sender , 'r' - receiver
	// 'p' - system ping
	// 'S' - server , 'C' - client
	char keylen;	// KEY length without name
	int format;		// SOCK_STREAM, SOCK_DGRAM ...
	char *ipv4;		// ip address v4
	char *info;		// info to be send
	int port;		// port
	int times;		// send times
	int delay;		// delay (s)
}Parameter;

int sockfdr = -1;	// receiver socket fd 
int sockfds = -1;	// sender socket fd

void destroy(int flag)
{	// deal ctrl+c
#ifdef WIN32
	if (sockfdr!=-1) closesocket(sockfdr);
	if (sockfds!=-1) closesocket(sockfds);
	WSACleanup();
#endif
#ifdef __linux__
	if (sockfdr!=-1) close(sockfdr);
	if (sockfds!=-1) close(sockfds);
#endif
	printf("...user cancel test, clean ok...\n");
	exit(0);
}

int usages()
{
	printf("A Simple TCP/UDP Connection Tester v1.0\n");
	printf("Usages: -s  IPv4  : [Sender]   The destination IP to be send\n");
	printf("        -r [IPv4] : [Receiver] Receive data from any or this IP\n");
	printf("        -p  PORT  : [Both]     Connection port to be send or listen, recommend > 10000 \n");
	printf("        -f FORMAT : [Both]     Connection format: tcp/udp/raw/pack/seq \n");
	printf("        -i \"info\" : [Sender]   Info to be send, using \" around it better\n");
	printf("       [-t  TIMES]: [Sender]   Send times, optional \n");
	printf("       [-d  DELAY]: [Sender]   Send delay between each data (s), optional\n");
	printf("       [-P       ]: [Native]   Using System Native Ping (Must at first)\n");
	printf("       [-S  KEY  ]: [Sniffe]   Support service KEY for net sniffer,using : split name\n");
	printf("       [-C IPv4_3]: [Sniffe]   LAN IPv4 segment for sniffer\n");
	printf("Using -h SINGLE to show this help \n");
	return 0;
}

int parser(Parameter *param, int argc, char *argv[]){
	memset(param,0,sizeof(*param));
	int i;
	for(i = 1; i < argc; ++i){
		if (strlen(argv[i])!=2 || argv[i][0]!='-')
			exit(printf("param %s error! using -h for help\n"
						,argv[i]));
		switch(argv[i][1]){
			case 'h':
				exit(usages());
			case 's':
				param->sr = 's';
				if (++i>=argc || inet_addr(argv[i])==INADDR_NONE)
					exit(printf("param %s error! using -h for help\n",argv[i]));
				param->ipv4 = argv[i];
				break;
			case 'C':
				param->sr = 'C';
				if (++i>=argc || argv[i][0]=='-')
					exit(printf("param %s error! using -h for help\n",argv[i]));
				param->ipv4 = argv[i];
				break;
			case 'S':
				param->sr = 'S';
				if (++i>=argc || argv[i][0]=='-')
					exit(printf("param %s error! using -h for help\n",argv[i]));
				param->info = argv[i];
				for(param->keylen=0;param->keylen<strlen(argv[i]) 
						&& argv[i][param->keylen]!=':';++param->keylen);
				break;
			case 'r':
				param->sr = 'r';
				if (i+1<argc && inet_addr(argv[i+1])!=INADDR_NONE)
					param->ipv4 = argv[++i];
				break;
			case 'p':
				if (++i>=argc || sscanf(argv[i],"%d",&(param->port))==-1)
					exit(printf("param %s error! using -h for help\n",argv[i]));
				break;
			case 'P':
				param->sr = 'P';
				return 0;
			case 'f':
				if (++i<argc){
					if(argv[i][0]=='t') param->format = SOCK_STREAM;
					if(argv[i][0]=='u') param->format = SOCK_DGRAM;
					if(argv[i][0]=='r') param->format = SOCK_RAW;
#ifdef __linux__
					if(argv[i][0]=='p') param->format = SOCK_PACKET;
					if(argv[i][0]=='s') param->format = SOCK_SEQPACKET;
#endif
				}
				else exit(printf("param %s error! using -h for help\n",argv[i]));
				break;
			case 'i':
				if (++i>=argc) exit(printf("param %s error! using -h for help\n",argv[i]));
				if (strlen(argv[i])>= BUFSIZE){
					printf("send info too large, cutdown to %d bytes\n",BUFSIZE);
					argv[i][BUFSIZE]='\0';
				}
				param->info = argv[i];
				break;
			case 't':
				if (++i>=argc || sscanf(argv[i],"%d",&(param->times))==-1)
					exit(printf("param %s error! using -h for help\n",argv[i]));
				break;
			case 'd':
				if (++i>=argc || sscanf(argv[i],"%d",&(param->delay))==-1)
					exit(printf("param %s error! using -h for help\n",argv[i]));
				break;
			default:
				exit(printf("param %s error! using -h for help\n",argv[i]));
				break;
		}
	}
	if (param->sr !='S' && param->sr != 'C')
		if (param->format==0)
			exit(printf("missing socket format , using -h for help\n"));
	if (param->port==0)
		exit(printf("missing port , using -h for help\n"));
	if (param->sr=='s'){
		if (strlen(param->ipv4)<1)
			exit(printf("missing ipv4 , using -h for help\n"));
		if (strlen(param->info)<1)
			exit(printf("missing send info , using -h for help\n"));
	}
	return 0;
}

int receiver_tcp(Parameter *param)
{
	struct sockaddr_in addrr,addrs;
	struct linger dolin;
	char buffer[BUFSIZE];
	socklen_t addrlen;
	int opt,rec;

	memset(&addrr,0,sizeof(addrr));
	addrr.sin_family = AF_INET;
	addrr.sin_addr.s_addr = (param->ipv4 ? 
			inet_addr(param->ipv4):htonl(INADDR_ANY));
	addrr.sin_port = htons(param->port);
	if ((sockfdr=socket(AF_INET,param->format,0))<0)
		return printf("create TCP socket failed\n");

	dolin.l_onoff=0;
	dolin.l_linger=1;
	opt = 1;
	setsockopt(sockfdr,SOL_SOCKET,SO_LINGER,&dolin,sizeof(dolin));
	setsockopt(sockfdr,SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt));
	setsockopt(sockfdr,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	if (bind(sockfdr,(struct sockaddr*)&addrr,sizeof(addrr))==-1){
		close(sockfdr);
		return printf("bind TCP port %d failed\n",param->port);
	}
	if(listen(sockfdr,5) < 0){
		close(sockfdr);
		return printf("listen TCP port %d error\n",param->port);
	}
	printf("listening port %6d format TCP waiting ...\n",param->port);

	for(rec=0;1;){
		memset(buffer,0,BUFSIZE);
		addrlen = sizeof(addrs);

		if ((sockfds=accept(sockfdr,(struct sockaddr*)&addrs,&addrlen))<0)
			continue;
		recv(sockfds,buffer,BUFSIZE,0);
#ifdef WIN32
		closesocket(sockfds);
#endif
#ifdef __linux__
		close(sockfds);
#endif
		sockfds = -1;
		printf("[%3d] receive TCP from %s port %d info : %s\n",
				rec++,inet_ntoa(addrs.sin_addr),
				ntohs(addrs.sin_port),buffer);
	}
#ifdef WIN32
	closesocket(sockfdr);
#endif
#ifdef __linux__
	close(sockfdr);
#endif
	return 0;
}

int sender_tcp(Parameter *param)
{
	struct sockaddr_in addrr;
	struct timeval tv_out;
	struct linger dolin;
	int i,rec,opt;

	memset(&addrr,0,sizeof(addrr));
	addrr.sin_family = AF_INET;
	addrr.sin_addr.s_addr = inet_addr(param->ipv4);
	addrr.sin_port = htons(param->port);

	for(i=rec=0;i<param->times;++i){
#ifdef WIN32
		Sleep(param->delay*1000);
#endif
#ifdef __linux__
		sleep(param->delay);
#endif
		if ((sockfds=socket(AF_INET,param->format,0))<0)
			return printf("create TCP socket failed\n");

		dolin.l_onoff=0;
		dolin.l_linger=1;
		opt=1;
		tv_out.tv_sec = param->delay;
		tv_out.tv_usec = 0;
		setsockopt(sockfds,SOL_SOCKET,SO_LINGER,&dolin,sizeof(dolin));
		setsockopt(sockfds,SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt));
		setsockopt(sockfds,SOL_SOCKET,SO_RCVTIMEO,&tv_out,sizeof(tv_out));
		setsockopt(sockfds,SOL_SOCKET,SO_SNDTIMEO,&tv_out,sizeof(tv_out));

		if (connect(sockfds,(struct sockaddr*)&addrr,sizeof(addrr))<0){
			printf("[%3d] connect to %s port %d error\n",i,param->ipv4,param->port);
#ifdef WIN32
			closesocket(sockfds);
#endif
#ifdef __linux__
			close(sockfds);
#endif
			sockfds=-1;
			continue;
		}

		if (send(sockfds,param->info,BUFSIZE,0)<0){
			printf("[%3d] send TCP to %s port %d error - info : %s\n",
					i,param->ipv4,param->port,param->info);
#ifdef WIN32
			closesocket(sockfds);
#endif
#ifdef __linux__
			close(sockfds);
#endif
			sockfds=-1;
			continue;
		}
		++rec;
		printf("[%3d] send TCP to %s port %d info : %s\n",
				i,param->ipv4,param->port,param->info);
	}

	printf("------ total send %d TCP packet, success = %d , loss = %d (%2d%%) -----\n"
			,param->times,rec,param->times-rec,100-100*rec/param->times);
	return 0;
}

int receiver_udp(Parameter *param)
{
	struct sockaddr_in addrr,addrs;
	struct linger dolin;
	char buffer[BUFSIZE];
	socklen_t addrlen;
	int opt,rec;

	memset(&addrr,0,sizeof(addrr));
	addrr.sin_family = AF_INET;
	addrr.sin_addr.s_addr = (param->ipv4 ? 
			inet_addr(param->ipv4):htonl(INADDR_ANY));
	addrr.sin_port = htons(param->port);
	if ((sockfdr=socket(AF_INET,param->format,0))<0)
		return printf("create UDP socket failed\n");

	dolin.l_onoff=0;
	dolin.l_linger=1;
	opt = 1;
	setsockopt(sockfdr,SOL_SOCKET,SO_LINGER,&dolin,sizeof(dolin));
	setsockopt(sockfdr,SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt));
	setsockopt(sockfdr,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	if (bind(sockfdr,(struct sockaddr*)&addrr,sizeof(addrr))==-1){
#ifdef WIN32
		closesocket(sockfdr);
#endif
#ifdef __linux__
		close(sockfdr);
#endif
		return printf("bind UDP failed\n");
	}
	printf("listening port %6d format UDP waiting ...\n",param->port);

	for(rec=0;1;){
		memset(buffer,0,BUFSIZE);
		addrlen = sizeof(addrs);

		if (recvfrom(sockfdr,buffer,BUFSIZE,0,
					(struct sockaddr*)&addrs,&addrlen)==-1)
			continue;
		if (sendto(sockfdr,buffer,BUFSIZE,0,
					(struct sockaddr*)&addrs,sizeof(addrs))<0)
			printf("send UDP confirm information failed\n");

		printf("[%3d] receive UDP ok from %s port %d info : %s\n",
				rec++,inet_ntoa(addrs.sin_addr),
				ntohs(addrs.sin_port),buffer);
	}
#ifdef WIN32
	closesocket(sockfdr);
#endif
#ifdef __linux__
	close(sockfdr);
#endif
	return 0;
}

int sender_udp(Parameter *param)
{
	struct sockaddr_in addrr;
	struct timeval tv_out;
	struct linger dolin;
	char buffer[BUFSIZE];
	socklen_t addrlen;
	int i,rec,opt;

	memset(&addrr,0,sizeof(addrr));
	addrr.sin_family = AF_INET;
	addrr.sin_addr.s_addr = inet_addr(param->ipv4);
	addrr.sin_port = htons(param->port);
	addrlen = sizeof(addrr);
	if ((sockfds=socket(AF_INET,param->format,0))<0)
		return printf("create UDP socket failed\n");

	dolin.l_onoff=0;
	dolin.l_linger=1;
	opt=1;
	tv_out.tv_sec = param->delay;
	tv_out.tv_usec = 0;
	setsockopt(sockfds,SOL_SOCKET,SO_LINGER,&dolin,sizeof(dolin));
	setsockopt(sockfds,SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt));
	setsockopt(sockfds,SOL_SOCKET,SO_RCVTIMEO,&tv_out,sizeof(tv_out));
	setsockopt(sockfds,SOL_SOCKET,SO_SNDTIMEO,&tv_out,sizeof(tv_out));

	for(i=rec=0;i<param->times;++i){
#ifdef WIN32
		Sleep(param->delay*1000);
#endif
#ifdef __linux__
		sleep(param->delay);
#endif
		if (sendto(sockfds,param->info,BUFSIZE,0,
					(struct sockaddr*)&addrr,sizeof(addrr))<0){
			printf("[%3d] send UDP to %s port %d error - info : %s\n",
					i,param->ipv4,param->port,param->info);
			continue;
		}
		printf("[%3d] send UDP to %s port %d info : %s\n",
				i,param->ipv4,param->port,param->info);
		addrlen = sizeof(addrr);
		memset(buffer,0,BUFSIZE);
		if (recvfrom(sockfds,buffer,BUFSIZE,0,
					(struct sockaddr*)&addrr,&addrlen)<0){
			printf("[%3d] recv confirm error from %s port %d\n",
					i,param->ipv4,param->port);
		}
		else{
			printf("[%3d] recv confirm ok from %s port %d\n",
					i,param->ipv4,param->port);
			++rec;
		}
	}

	printf("------ total send %d UDP packet, success = %d , loss = %d (%2d%%) -----\n"
			,param->times,rec,param->times-rec,100-100*rec/param->times);
	return 0;
}

int receiver_sniff(Parameter *param)
{
	struct sockaddr_in addrr,addrs;
	struct linger dolin;
	char buffer[BUFSIZE];
	socklen_t addrlen;
	int opt,rec;

	memset(&addrr,0,sizeof(addrr));
	addrr.sin_family = AF_INET;
	addrr.sin_addr.s_addr = htonl(INADDR_ANY);
	addrr.sin_port = htons(param->port);
	if ((sockfdr=socket(AF_INET,param->format,0))<0)
		return printf("create Sniffe Server UDP socket failed\n");

	dolin.l_onoff=0;
	dolin.l_linger=1;
	opt = 1;
	setsockopt(sockfdr,SOL_SOCKET,SO_LINGER,&dolin,sizeof(dolin));
	setsockopt(sockfdr,SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt));
	setsockopt(sockfdr,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	if (bind(sockfdr,(struct sockaddr*)&addrr,sizeof(addrr))==-1){
#ifdef WIN32
		closesocket(sockfdr);
#endif
#ifdef __linux__
		close(sockfdr);
#endif
		return printf("bind Sniffe Server UDP failed\n");
	}
	printf("listening port %6d KEY %s for Sniffer through UDP...\n",param->port,param->info);

	for(rec=0;1;){
		memset(buffer,0,BUFSIZE);
		addrlen = sizeof(addrs);

		if (recvfrom(sockfdr,buffer,BUFSIZE,0,
					(struct sockaddr*)&addrs,&addrlen)==-1)
			continue;
		if (strlen(buffer)!=param->keylen || strncmp(buffer,param->info,param->keylen)){
			printf("receive Unknown KEY %s from %s port %d ignore\n",
					buffer,inet_ntoa(addrs.sin_addr),ntohs(addrs.sin_port));
			continue;
		}
		if (sendto(sockfdr,param->info,BUFSIZE,0,
					(struct sockaddr*)&addrs,sizeof(addrs))<0){
			printf("send details to Sniffer %s port %d failed\n",
					inet_ntoa(addrs.sin_addr),ntohs(addrs.sin_port));
			continue;
		}

		printf("[%3d] send %s to Sniffer %s port %d ok\n",
				rec++,param->info,inet_ntoa(addrs.sin_addr),
				ntohs(addrs.sin_port));
	}
#ifdef WIN32
	closesocket(sockfdr);
#endif
#ifdef __linux__
	close(sockfdr);
#endif
	return 0;
}

int sender_sniff(Parameter *param)
{
	struct sockaddr_in addrr;
	struct timeval tv_out;
	char buffer[BUFSIZE];
	socklen_t addrlen;
	int i,j,rec;

	if ((sockfds=socket(AF_INET,param->format,0))<0)
		return printf("create Sniffer UDP socket failed\n");
	tv_out.tv_sec = 0;
	tv_out.tv_usec = 1+param->delay;
	setsockopt(sockfds,SOL_SOCKET,SO_RCVTIMEO,&tv_out,sizeof(tv_out));
	setsockopt(sockfds,SOL_SOCKET,SO_SNDTIMEO,&tv_out,sizeof(tv_out));

	for(i=rec=0;i<256;++i){
		memset(&addrr,0,sizeof(addrr));
		addrr.sin_family = AF_INET;
		addrr.sin_port = htons(param->port);
		sprintf(buffer,"%s.%d",param->ipv4,i);
		addrr.sin_addr.s_addr = inet_addr(buffer);
		addrlen = sizeof(addrr);

		//printf("[%3d] Test Server %s from %s.%d\n",++rec,buffer,param->ipv4,i);
		for(j=0;j<param->times;++j){
			if (sendto(sockfds,param->info,BUFSIZE,0,
						(struct sockaddr*)&addrr,sizeof(addrr))>=0){
				memset(buffer,0,BUFSIZE);
				if (recvfrom(sockfds,buffer,BUFSIZE,0,
							(struct sockaddr*)&addrr,&addrlen)>=0){
					printf("[%3d] Find Server %s from %s.%d\n",
							++rec,buffer,param->ipv4,i);
					break;
				}
			}
		}	
	}
#ifdef WIN32
	closesocket(sockfdr);
#endif
#ifdef __linux__
	close(sockfdr);
#endif
	return 0;
}

int main(int argc,char *argv[])
{
	Parameter param;
	char cmd[CMDLEN]="ping";
	int i;
	parser(&param,argc,argv);

	if (param.sr=='P'){
		for(i=1;++i<argc && strcat(cmd," ");strcat(cmd,argv[i]));
		printf("Using system ping : %s\n",cmd);
		system(cmd);
		return 0;
	}
	signal(SIGINT,destroy);

#ifdef WIN32
	WSADATA wsadata;
	if (WSAStartup(MAKEWORD(1,1),&wsadata)==SOCKET_ERROR){
		printf("WSAStartup failed\n");
		exit(0);
	}
#endif
	if (param.sr == 'r') {
		switch(param.format){
			case SOCK_STREAM:
				receiver_tcp(&param);
				break;
			case SOCK_DGRAM:
				receiver_udp(&param);
				break;
			default:
				printf("Unknown socket format type!\n");
				return 0;
		}
	}
	else if (param.sr == 's') {
		switch(param.format){
			case SOCK_STREAM:
				sender_tcp(&param);
				break;
			case SOCK_DGRAM:
				sender_udp(&param);
				break;
			default:
				printf("Unknown socket format type!\n");
				return 0;
		}
	}
	else if (param.sr == 'S')
		receiver_sniff(&param);
	else if (param.sr == 'C')
		sender_sniff(&param);
	else printf("Unknown error\n");
#ifdef WIN32
	WSACleanup();
#endif
	return 0;
}
